今天試寫Python的多型。
左邊是「同名異式」(overloading)。又可細分為「方法同名異式」(method overloading)和「運算子同名異式」(operator overloading)兩大類。運算子方面在多型第一篇(Day 27)的註解就有提及,這裡不再贅述。
至於方法的同名異式,筆者已一再陳述,Python並不支援。既不支援又有甚麼好講?原來有人拿len()
等標準函數為例,說len()的參數可以是個list,也可以是str(正確講法是:len()的參數要是個sequence或collection),所以len()就是個poly-morphic函數。
又例如下面的sum_2_or_3_ints()
函數,可以接受兩個或者三個整數型別的參數,有人認為這也算是多型:
def sum_2_or_3_ints(n1: int, n2: int, n3: int=0):
return n1 + n2 + n3
print(sum_2_or_3_ints(3, 4)) # 7
print(sum_2_or_3_ints(2, 5, 8)) # 15
好吧,多型本來就是多種型態之意,那麼有多種不同型態的多型也未嘗不可。只是筆者並不認同就是。
筆者所持論點是:真正的多型是要依不同物件去執行不同方法。即使是overloading也是不同方法。但以上的標準函數len()
和自訂函數sum_2_or_3_ints()
,就僅此一家別無分號,說也是多型,筆者覺得似乎有點過度解釋。
筆者認為這或許勉強算是Python的多型起點。
class EarthlyTree(): # 地球上的樹
def __init__(self, breed: str, age: int): # constructor
is_valid_breed = True # 判斷條件略。
if is_valid_breed:
self.__breed = breed
else:
raise Exception('Invalid breed.')
is_valid_age = True # 判斷條件略。
if is_valid_age:
self.__age = age
else:
raise Exception('Invalid age.')
@property
def breed(self) -> str:
return self.__breed
@breed.setter
def breed(self, breed: str):
is_valid_breed = True # 判斷條件略。
if is_valid_breed:
self.__breed = breed
else:
raise Exception('Invalid breed.')
@property
def age(self) -> int:
return self.__age
@age.setter
def age(self, breed: str):
is_valid_age = True # 判斷條件略。
if is_valid_age:
self.__age = age
else:
raise Exception('Invalid age.')
def grow(self): # 生長。
print(f'{__class__.__name__} {self.breed} is growing.')
def reproduce(self): # 繁殖。
print(f'{__class__.__name__} {self.breed} is reproducing.')
def get_sick(self): # 得病。
print(f'{__class__.__name__} {self.breed} is getting sick.')
def die(self): # 死亡(假設死亡也有「行為」)。
print(f'{__class__.__name__} {self.breed} is dying.')
class VenusianTree(): # 金星樹
def __init__(self, breed: str, age: int): # constructor
is_valid_breed = True # 判斷條件略。
if is_valid_breed:
self.__breed = breed
else:
raise Exception('Invalid breed.')
is_valid_age = True # 判斷條件略。
if is_valid_age:
self.__age = age
else:
raise Exception('Invalid age.')
@property
def breed(self) -> str:
return self.__breed
@breed.setter
def breed(self, breed: str):
is_valid_breed = True # 判斷條件略。
if is_valid_breed:
self.__breed = breed
else:
raise Exception('Invalid breed.')
@property
def age(self) -> int:
return self.__age
@age.setter
def age(self, breed: str):
is_valid_age = True # 判斷條件略。
if is_valid_age:
self.__age = age
else:
raise Exception('Invalid age.')
def grow(self): # 生長。
print(f'{__class__.__name__} {self.breed} grows younger and younger.')
def reproduce(self): # 繁殖。
print(f'{__class__.__name__} {self.breed} reproduces on a daily basis.')
def get_sick(self): # 得病。
print(f'{__class__.__name__} {self.breed} always revovers from illness.')
def die(self): # 死亡(假設死亡也有「行為」)。
print(f'{__class__.__name__} {self.breed} raises itself from the dead.')
class MartianTree(): # 火星樹
def __init__(self, breed: str, age: int): # constructor
is_valid_breed = True # 判斷條件略。
if is_valid_breed:
self.__breed = breed
else:
raise Exception('Invalid breed.')
is_valid_age = True # 判斷條件略。
if is_valid_age:
self.__age = age
else:
raise Exception('Invalid age.')
@property
def breed(self) -> str:
return self.__breed
@breed.setter
def breed(self, breed: str):
is_valid_breed = True # 判斷條件略。
if is_valid_breed:
self.__breed = breed
else:
raise Exception('Invalid breed.')
@property
def age(self) -> int:
return self.__age
@age.setter
def age(self, breed: str):
is_valid_age = True # 判斷條件略。
if is_valid_age:
self.__age = age
else:
raise Exception('Invalid age.')
def grow(self): # 生長。
print(f'{__class__.__name__} {self.breed} stops growing.')
def reproduce(self): # 繁殖。
print(f'{__class__.__name__} {self.breed} does not reproduce.')
def get_sick(self): # 得病。
print(f'{__class__.__name__} {self.breed} is healthy.')
def die(self): # 死亡(假設死亡也有「行為」)。
print(f'{__class__.__name__} {self.breed} is immortal.')
# 主程式
def universe(tree: object): # 參數可為任何物件?
tree.grow()
tree.reproduce()
tree.get_sick()
tree.die()
tree_on_earth = EarthlyTree('cedar', 2_500)
tree_on_venus = VenusianTree('varan', -3_000)
tree_on_mars = MartianTree('mvule', 500_000_000)
print()
universe(tree_on_earth)
print()
universe(tree_on_venus)
print()
universe(tree_on_mars)
print()
輸出:
上例以不同類別的物件傳入universe()
函數,達成「類多型」效果。
不過這個程式存有危機,萬一傳入的物件並無grow()
, reproduce()
, get_sick()
, die()
等方法,鐵定出問題。
此外,本例並未用到繼承。別忘了:真正意義的多型一定源自繼承。繼承和覆寫(overriding)是多型的基礎和預備。沒有「繼承」,很難認定那是貨真價實的「多型」。不是孕育自蚌的珍珠是真珍珠嗎?
下面就讓我們以繼承來實作多型吧:
from abc import ABC, abstractmethod
class Tree(ABC):
def __init__(self, breed: str, age: int): # constructor
is_valid_breed = True # 判斷條件略。
if is_valid_breed:
self.__breed = breed
else:
raise Exception('Invalid breed.')
is_valid_age = True # 判斷條件略。
if is_valid_age:
self.__age = age
else:
raise Exception('Invalid age.')
@property
def breed(self) -> str:
return self.__breed
@abstractmethod
def grow(self): # 生長(樹的共同行為)
...
@abstractmethod # 繁殖(樹的共同行為)
def reproduce(self):
...
@abstractmethod # 生病(樹的共同行為)
def get_sick(self):
...
@abstractmethod # 死亡(樹的共同行為)
def die(self):
...
class EarthlyTree(Tree): # 地球上的樹
def __init__(self, breed: str, age: int): # constructor
is_valid_breed = True # 判斷條件略。
if is_valid_breed:
self.__breed = breed
else:
raise Exception('Invalid breed.')
is_valid_age = True # 判斷條件略。
if is_valid_age:
self.__age = age
else:
raise Exception('Invalid age.')
@property
def breed(self) -> str:
return self.__breed
@breed.setter
def breed(self, breed: str):
is_valid_breed = True # 判斷條件略。
if is_valid_breed:
self.__breed = breed
else:
raise Exception('Invalid breed.')
@property
def age(self) -> int:
return self.__age
@age.setter
def age(self, breed: str):
is_valid_age = True # 判斷條件略。
if is_valid_age:
self.__age = age
else:
raise Exception('Invalid age.')
def grow(self): # 生長。
print(f'{__class__.__name__} {self.breed} is growing.')
def reproduce(self): # 繁殖。
print(f'{__class__.__name__} {self.breed} is reproducing.')
def get_sick(self): # 得病。
print(f'{__class__.__name__} {self.breed} is getting sick.')
def die(self): # 死亡(假設死亡也有「行為」)。
print(f'{__class__.__name__} {self.breed} is dying.')
class VenusianTree(Tree): # 金星樹
def __init__(self, breed: str, age: int): # constructor
is_valid_breed = True # 判斷條件略。
if is_valid_breed:
self.__breed = breed
else:
raise Exception('Invalid breed.')
is_valid_age = True # 判斷條件略。
if is_valid_age:
self.__age = age
else:
raise Exception('Invalid age.')
@property
def breed(self) -> str:
return self.__breed
@breed.setter
def breed(self, breed: str):
is_valid_breed = True # 判斷條件略。
if is_valid_breed:
self.__breed = breed
else:
raise Exception('Invalid breed.')
@property
def age(self) -> int:
return self.__age
@age.setter
def age(self, breed: str):
is_valid_age = True # 判斷條件略。
if is_valid_age:
self.__age = age
else:
raise Exception('Invalid age.')
def grow(self): # 生長。
print(f'{__class__.__name__} {self.breed} grows younger and younger.')
def reproduce(self): # 繁殖。
print(f'{__class__.__name__} {self.breed} reproduces on a daily basis.')
def get_sick(self): # 得病。
print(f'{__class__.__name__} {self.breed} always revovers from illness.')
def die(self): # 死亡(假設死亡也有「行為」)。
print(f'{__class__.__name__} {self.breed} raises itself from the dead.')
class MartianTree(Tree): # 火星樹
def __init__(self, breed: str, age: int): # constructor
is_valid_breed = True # 判斷條件略。
if is_valid_breed:
self.__breed = breed
else:
raise Exception('Invalid breed.')
is_valid_age = True # 判斷條件略。
if is_valid_age:
self.__age = age
else:
raise Exception('Invalid age.')
@property
def breed(self) -> str:
return self.__breed
@breed.setter
def breed(self, breed: str):
is_valid_breed = True # 判斷條件略。
if is_valid_breed:
self.__breed = breed
else:
raise Exception('Invalid breed.')
@property
def age(self) -> int:
return self.__age
@age.setter
def age(self, breed: str):
is_valid_age = True # 判斷條件略。
if is_valid_age:
self.__age = age
else:
raise Exception('Invalid age.')
def grow(self): # 生長。
print(f'{__class__.__name__} {self.breed} stops growing.')
def reproduce(self): # 繁殖。
print(f'{__class__.__name__} {self.breed} does not reproduce.')
def get_sick(self): # 得病。
print(f'{__class__.__name__} {self.breed} is healthy.')
def die(self): # 死亡(假設死亡也有「行為」)。
print(f'{__class__.__name__} {self.breed} is immortal.')
隨機抽兩棵樹看看:
import random as r
tree_on_earth = EarthlyTree('cedar', 2_500)
tree_on_venus = VenusianTree('varan', -3_000)
tree_on_mars = MartianTree('mvule', 500_000_000)
trees = [tree_on_earth, tree_on_venus, tree_on_mars]
randomly_selected_trees = r.sample(trees, k=2)
for tree in randomly_selected_trees:
tree.grow()
tree.reproduce()
tree.get_sick()
tree.die()
print()
隨機產生金星樹和火星樹:
再試在程式執行時期,從鍵盤輸入樹種,看能否動態呼叫到正確的方法:
tree_infos = {'Earth': {'breed': 'cedar', 'age': 2_500},
'Venus': {'breed': 'varan', 'age': -3_000},
'Mars': {'breed': 'mvule', 'age': 500_000_000}
}
tree_on_earth = EarthlyTree(tree_infos['Earth']['breed'], tree_infos['Earth']['age'])
tree_on_venus = VenusianTree(tree_infos['Venus']['breed'], tree_infos['Venus']['age'])
tree_on_mars = MartianTree(tree_infos['Mars']['breed'], tree_infos['Mars']['age'])
trees = {tree_infos['Earth']['breed']: tree_on_earth, tree_infos['Venus']['breed']: tree_on_venus, tree_infos['Mars']['breed']: tree_on_mars}
breed = input('Enter a tree breed: ').strip().lower()
tree = trees.get(breed)
if tree is not None:
tree.grow()
tree.reproduce()
tree.get_sick()
tree.die()
print()
else:
print('Oh, this tree is not in our list. Pls check the spelling.')
結果成功配對到正確的方法,並無張冠李戴,錯把馮京當馬涼:
註1: 英語教學時間:靜態、動態型別語言,英文的正確寫法是statically typed language
及dynamically typed language
,或簡稱static language
和dynamic language
。但不能寫static type language
或dynamic type language
(因不合英文文法)。如果指涉的是「型別系統」本身,通常用gerund:static typing
或dynamic typing
。抱歉,筆者英語老師出身,職業病。